﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Xml.Serialization;
using System.Runtime.CompilerServices;
using System.Xml.Linq;
using System.Xml.XPath;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using System.Diagnostics;

namespace Ghost.Tasks.Wiki.XApi
{
    public partial class XAssembly
    {
        [XmlIgnore]
        public Dictionary<string, XMember> members = new Dictionary<string, XMember>();

        [XmlIgnore]
        public XProject project;

        [XmlIgnore]
        public Assembly inputAssembly;

        [XmlIgnore]
        public string xapiPath;

        [XmlIgnore]
        public Dictionary<string, MethodInfo> extensionMethodLookup;

        [XmlIgnore]
        public IDictionary<string, XElement> memberElements;

    }

    public static class XAssemblyExtensions
    {

        /// <summary>
        /// Builds the type.
        /// </summary>
        /// <param name="typeElement">The type element.</param>
        /// <param name="xid">The id.</param>
        /// <returns></returns>
        private static XType BuildXType(this XAssembly assembly, Type type, XElement typeElement, string xid)
        {
            XType output = null;

            //Now create an instance
            if (type.IsClass)
            {
                if (type.IsGenericType)
                {
                    output = new XGeneric();
                }
                else
                {
                    output = new XClass();
                }
            }
            else if (type.IsEnum)
            {
                output = new XEnum();
            }
            else if (type.IsInterface)
            {
                output = new XInterface();
            }
            else if (type.IsValueType)
            {
                output = new XStruct();
            }
            else
            {
                output = null;
            }
            if (output == null)
                return null;
            output.log = assembly.log;
            output.name = type.Name;
            output.assembly = assembly;
            //Fill common parameters
            output.id = xid;

            //Provide a way to unravel the nesting
            if (output is XStruct && type.DeclaringType != null)
                (output as XStruct).parentStruct = assembly.FindOrBuildXType(type.DeclaringType, type.DeclaringType.ToXmlCommentID()) as XStruct;

            output.caption = type.Name;
            output.ns = type.Namespace;
            output.type = type;
            output.isPublic = type.IsPublic;
            output.wikiLink = xid.ToWikiLink();
            output.sidebar = output.wikiLink + "_Sidebar";

            if (typeElement != null)
                output.BuildComments(typeElement);

            if (type.IsClass)
            {
                XClass @class = output as XClass;
                if (@class != null)
                {
                    @class.isNested = type.IsNested;
                    @class.isSealed = type.IsSealed;
                    @class.isAbstract = type.IsAbstract;
                    @class.field = output.BuildXFields(type, typeElement).ToArray();
                    @class.property = output.BuildXProperties(type, typeElement).ToArray();
                    @class.method = output.BuildXMethods(type, typeElement).ToArray();
                    @class.@event = output.BuildXEvents(type, typeElement).ToArray();
                }

                if (type.IsGenericType)
                {
                
                }
                else
                {
                
                }
            }
            else if (type.IsEnum)
            {
                (output as XEnum).field = output.BuildXFields(type, typeElement).ToArray();
            }
            else if (type.IsInterface)
            {
                XInterface @interface = output as XInterface;
                if (@interface != null)
                {
                    @interface.isNested = type.IsNested;
                    @interface.field = output.BuildXFields(type, typeElement).ToArray();
                    @interface.property = output.BuildXProperties(type, typeElement).ToArray();
                    @interface.method = output.BuildXMethods(type, typeElement).ToArray();
                    @interface.@event = output.BuildXEvents(type, typeElement).ToArray();
                }
            }
            else if (type.IsValueType)
            {
                XStruct @struct = output as XStruct;
                if (@struct != null)
                {
                    @struct.isNested = type.IsNested;
                    @struct.isSealed = type.IsSealed;
                    @struct.field = output.BuildXFields(type, typeElement).ToArray();
                    @struct.property = output.BuildXProperties(type, typeElement).ToArray();
                    @struct.method = output.BuildXMethods(type, typeElement).ToArray();
                    @struct.@event = output.BuildXEvents(type, typeElement).ToArray();
                }
            }

            if (type.IsNested)
            {
                output.caption = string.Concat(type.DeclaringType.Name, ".", output.caption);
            }

            return output;
        }

        public static XElement GetCommentElement(this XAssembly assembly, string xid)
        {
            XElement ret = null;
            assembly.memberElements.TryGetValue(xid, out ret);
            if (ret == null)
            {
                assembly.LogWarning("Could not find XML comments for \"{0}\"", xid);
            }
            return ret;
        }

        public static XType FindOrBuildXType(this XAssembly assembly, Type type, string xid)
        {
            XMember xMember;
            XType xType;
            if (assembly.members.TryGetValue(xid, out xMember) && (xType = xMember as XType) != null )
                return xType;

            XElement element = GetCommentElement(assembly, xid);
            xType = BuildXType(assembly, type, element, xid);
            assembly.members.Add(xid, xType);
            assembly.project.AddMember(xType);
            return xType;
        }    

         public static IEnumerable<XType> GetTypes(this XAssembly output)
        {
            return output.project.Members.OfType<XType>().Where(xt => xt.assembly.id == output.id);
        }

        public static XNameSpace BuildNameSpace(this XAssembly outputParent, string nameSpace)
        {
            XNameSpace output = new XNameSpace();
            output.id = string.Format("N:{0}", nameSpace);
            //Log.LogMessageFromText(string.Format("Built Namespace : {0}", output.id), MessageImportance.Normal);
            output.wikiLink = output.id.ToWikiLink();
            output.sidebar = output.wikiLink.ToSidebar();
            output.caption = nameSpace;
            output.BuildFileComments(outputParent);
            outputParent.project.AddMember(output);
            return output;
        }

        public static void BuildExtensionMethods(this XAssembly output)
        {
            List<MethodInfo> extensionMethods = GetExtensionMethods(output.inputAssembly).ToList();
            output.extensionMethodLookup = new Dictionary<string, MethodInfo>(extensionMethods.Count);

            foreach (MethodInfo mi in extensionMethods)
            {     
                StringBuilder sb = new StringBuilder();
                sb.AppendFormat("M:{0}.{1}", mi.DeclaringType.FullName, mi.Name);
                if (mi.IsGenericMethodDefinition)
                    sb.Append("`");
                Type[] genericMethodArguments = mi.GetGenericArguments();
                if (genericMethodArguments != null && genericMethodArguments.Length > 0)
                    sb.AppendFormat("`{0}", genericMethodArguments.Length);
                ParameterInfo[] parameters = mi.GetParameters();
                if (parameters != null && parameters.Length > 0)
                {
                    sb.Append("(");
                    sb.Append(string.Join(",",parameters.Select(p => p.ParameterType).Select(pt => ToCref(pt)).ToArray()));
                    sb.Append(")");
                }
                MethodInfo existingExtensionMethod = null;
                if (output.extensionMethodLookup.TryGetValue(sb.ToString(), out existingExtensionMethod))
                {
                    output.LogWarning("Generated Extension Method Cref \"{0}\" is ambiguous\n\tExisting Method: \"{1}\"\n\tNew Method: \"{2}\"", sb.ToString(), existingExtensionMethod, mi);
                }
                else
                {
                    output.LogVerbose("Found Extension Method \"{0}\"\n\tGenerated Cref:\"{1}\"", mi, sb);
                    output.extensionMethodLookup.Add(sb.ToString(), mi);
                }

            }
        }

        private static string ToCref(Type pt)
        {
            StringBuilder sb = new StringBuilder();
            string fullName = pt.FullName ?? pt.Name;
            if (pt.IsGenericParameter)
            {
                if (pt.DeclaringMethod != null)
                {
                    //generic parameter defined for this method
                    return string.Format("``{0}", pt.GenericParameterPosition);
                }
                else if (pt.DeclaringType != null)
                {
                    return pt.DeclaringType.GetGenericArguments().First(t => t.Name == pt.Name).Name;
                }
            }
            else
            {
                if (pt.IsGenericType && !pt.IsGenericTypeDefinition) //get rid of the tic if we have no open generic parameters
                {
                    int ticLoc = fullName.LastIndexOf("`");
                    if (ticLoc >= 0)
                    {
                        fullName = fullName.Substring(0, ticLoc);
                    }
                }
                sb.Append(fullName);

                if (pt.IsGenericType)
                {
                    sb.Append("{");
                    foreach (Type genericParameter in pt.GetGenericArguments())
                    {
                        sb.Append(ToCref(genericParameter));
                    }
                    sb.Append("}");
                }
            }
            return sb.ToString();
        }

        static IEnumerable<MethodInfo> GetExtensionMethods(Assembly assembly, Type extendedType)
        {

            var query = from type in assembly.GetTypes()
                        where type.IsSealed && !type.IsGenericType && !type.IsNested
                        from method in type.GetMethods(BindingFlags.Static
                            | BindingFlags.Public | BindingFlags.NonPublic)
                        where method.IsDefined(typeof(ExtensionAttribute), false)
                        where method.GetParameters()[0].ParameterType == extendedType
                        select method;

            return query;
        }

        static IEnumerable<MethodInfo> GetExtensionMethods(Assembly assembly)
        {

            var query = from type in assembly.GetTypes()
                        where type.IsSealed && !type.IsGenericType && !type.IsNested
                        from method in type.GetMethods(BindingFlags.Static
                            | BindingFlags.Public | BindingFlags.NonPublic)
                        where method.IsDefined(typeof(ExtensionAttribute), false)
                        select method;

            return query;
        }

        public static bool TryGetExtensionMethod(this XAssembly output, string id, out MethodInfo methodInfo)
        {
            return output.extensionMethodLookup.TryGetValue(id, out methodInfo);
        }
    }
}
